// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:args/command_runner.dart';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_plugin_tools/src/common/core.dart';
import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
import 'package:flutter_plugin_tools/src/readme_check_command.dart';
import 'package:test/test.dart';

import 'mocks.dart';
import 'util.dart';

void main() {
  late CommandRunner<void> runner;
  late RecordingProcessRunner processRunner;
  late FileSystem fileSystem;
  late MockPlatform mockPlatform;
  late Directory packagesDir;

  setUp(() {
    fileSystem = MemoryFileSystem();
    mockPlatform = MockPlatform();
    packagesDir = fileSystem.currentDirectory.childDirectory('packages');
    createPackagesDirectory(parentDir: packagesDir.parent);
    processRunner = RecordingProcessRunner();
    final ReadmeCheckCommand command = ReadmeCheckCommand(
      packagesDir,
      processRunner: processRunner,
      platform: mockPlatform,
    );

    runner = CommandRunner<void>(
        'readme_check_command', 'Test for readme_check_command');
    runner.addCommand(command);
  });

  test('prints paths of checked READMEs', () async {
    final RepositoryPackage package = createFakePackage(
        'a_package', packagesDir,
        examples: <String>['example1', 'example2']);
    for (final RepositoryPackage example in package.getExamples()) {
      example.readmeFile.writeAsStringSync('A readme');
    }
    getExampleDir(package).childFile('README.md').writeAsStringSync('A readme');

    final List<String> output =
        await runCapturingPrint(runner, <String>['readme-check']);

    expect(
      output,
      containsAll(<Matcher>[
        contains('  Checking README.md...'),
        contains('  Checking example/README.md...'),
        contains('  Checking example/example1/README.md...'),
        contains('  Checking example/example2/README.md...'),
      ]),
    );
  });

  test('fails when package README is missing', () async {
    final RepositoryPackage package =
        createFakePackage('a_package', packagesDir);
    package.readmeFile.deleteSync();

    Error? commandError;
    final List<String> output = await runCapturingPrint(
        runner, <String>['readme-check'], errorHandler: (Error e) {
      commandError = e;
    });

    expect(commandError, isA<ToolExit>());
    expect(
      output,
      containsAllInOrder(<Matcher>[
        contains('Missing README.md'),
      ]),
    );
  });

  test('passes when example README is missing', () async {
    createFakePackage('a_package', packagesDir);

    final List<String> output =
        await runCapturingPrint(runner, <String>['readme-check']);

    expect(
      output,
      containsAllInOrder(<Matcher>[
        contains('No README for example'),
      ]),
    );
  });

  test('does not inculde non-example subpackages', () async {
    final RepositoryPackage package =
        createFakePackage('a_package', packagesDir);
    const String subpackageName = 'special_test';
    final RepositoryPackage miscSubpackage =
        createFakePackage(subpackageName, package.directory);
    miscSubpackage.readmeFile.delete();

    final List<String> output =
        await runCapturingPrint(runner, <String>['readme-check']);

    expect(output, isNot(contains(subpackageName)));
  });

  test('fails when README still has plugin template boilerplate', () async {
    final RepositoryPackage package = createFakePlugin('a_plugin', packagesDir);
    package.readmeFile.writeAsStringSync('''
## Getting Started

This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/developing-packages/),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.

For help getting started with Flutter development, view the
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
''');

    Error? commandError;
    final List<String> output = await runCapturingPrint(
        runner, <String>['readme-check'], errorHandler: (Error e) {
      commandError = e;
    });

    expect(commandError, isA<ToolExit>());
    expect(
      output,
      containsAllInOrder(<Matcher>[
        contains('The boilerplate section about getting started with Flutter '
            'should not be left in.'),
        contains('Contains template boilerplate'),
      ]),
    );
  });

  test('fails when example README still has application template boilerplate',
      () async {
    final RepositoryPackage package =
        createFakePackage('a_package', packagesDir);
    package.getExamples().first.readmeFile.writeAsStringSync('''
## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
''');

    Error? commandError;
    final List<String> output = await runCapturingPrint(
        runner, <String>['readme-check'], errorHandler: (Error e) {
      commandError = e;
    });

    expect(commandError, isA<ToolExit>());
    expect(
      output,
      containsAllInOrder(<Matcher>[
        contains('The boilerplate section about getting started with Flutter '
            'should not be left in.'),
        contains('Contains template boilerplate'),
      ]),
    );
  });

  test(
      'fails when multi-example top-level example directory README still has '
      'application template boilerplate', () async {
    final RepositoryPackage package = createFakePackage(
        'a_package', packagesDir,
        examples: <String>['example1', 'example2']);
    package.directory
        .childDirectory('example')
        .childFile('README.md')
        .writeAsStringSync('''
## Getting Started

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
''');

    Error? commandError;
    final List<String> output = await runCapturingPrint(
        runner, <String>['readme-check'], errorHandler: (Error e) {
      commandError = e;
    });

    expect(commandError, isA<ToolExit>());
    expect(
      output,
      containsAllInOrder(<Matcher>[
        contains('The boilerplate section about getting started with Flutter '
            'should not be left in.'),
        contains('Contains template boilerplate'),
      ]),
    );
  });

  group('plugin OS support', () {
    test(
        'does not check support table for anything other than app-facing plugin packages',
        () async {
      const String federatedPluginName = 'a_federated_plugin';
      final Directory federatedDir =
          packagesDir.childDirectory(federatedPluginName);
      // A non-plugin package.
      createFakePackage('a_package', packagesDir);
      // Non-app-facing parts of a federated plugin.
      createFakePlugin(
          '${federatedPluginName}_platform_interface', federatedDir);
      createFakePlugin('${federatedPluginName}_android', federatedDir);

      final List<String> output = await runCapturingPrint(runner, <String>[
        'readme-check',
      ]);

      expect(
        output,
        containsAll(<Matcher>[
          contains('Running for a_package...'),
          contains('Running for a_federated_plugin_platform_interface...'),
          contains('Running for a_federated_plugin_android...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('fails when non-federated plugin is missing an OS support table',
        () async {
      createFakePlugin('a_plugin', packagesDir);

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('No OS support table found'),
        ]),
      );
    });

    test(
        'fails when app-facing part of a federated plugin is missing an OS support table',
        () async {
      createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin'));

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('No OS support table found'),
        ]),
      );
    });

    test('fails the OS support table is missing the header', () async {
      final RepositoryPackage plugin =
          createFakePlugin('a_plugin', packagesDir);

      plugin.readmeFile.writeAsStringSync('''
A very useful plugin.

| **Support**    | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('OS support table does not have the expected header format'),
        ]),
      );
    });

    test('fails if the OS support table is missing a supported OS', () async {
      final RepositoryPackage plugin = createFakePlugin(
        'a_plugin',
        packagesDir,
        platformSupport: <String, PlatformDetails>{
          platformAndroid: const PlatformDetails(PlatformSupport.inline),
          platformIOS: const PlatformDetails(PlatformSupport.inline),
          platformWeb: const PlatformDetails(PlatformSupport.inline),
        },
      );

      plugin.readmeFile.writeAsStringSync('''
A very useful plugin.

|                | Android | iOS      |
|----------------|---------|----------|
| **Support**    | SDK 21+ | iOS 10+* |
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('  OS support table does not match supported platforms:\n'
              '    Actual:     android, ios, web\n'
              '    Documented: android, ios'),
          contains('Incorrect OS support table'),
        ]),
      );
    });

    test('fails if the OS support table lists an extra OS', () async {
      final RepositoryPackage plugin = createFakePlugin(
        'a_plugin',
        packagesDir,
        platformSupport: <String, PlatformDetails>{
          platformAndroid: const PlatformDetails(PlatformSupport.inline),
          platformIOS: const PlatformDetails(PlatformSupport.inline),
        },
      );

      plugin.readmeFile.writeAsStringSync('''
A very useful plugin.

|                | Android | iOS      | Web                    |
|----------------|---------|----------|------------------------|
| **Support**    | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('  OS support table does not match supported platforms:\n'
              '    Actual:     android, ios\n'
              '    Documented: android, ios, web'),
          contains('Incorrect OS support table'),
        ]),
      );
    });

    test('fails if the OS support table has unexpected OS formatting',
        () async {
      final RepositoryPackage plugin = createFakePlugin(
        'a_plugin',
        packagesDir,
        platformSupport: <String, PlatformDetails>{
          platformAndroid: const PlatformDetails(PlatformSupport.inline),
          platformIOS: const PlatformDetails(PlatformSupport.inline),
          platformMacOS: const PlatformDetails(PlatformSupport.inline),
          platformWeb: const PlatformDetails(PlatformSupport.inline),
        },
      );

      plugin.readmeFile.writeAsStringSync('''
A very useful plugin.

|                | android | ios      | MacOS | web                    |
|----------------|---------|----------|-------|------------------------|
| **Support**    | SDK 21+ | iOS 10+* | 10.11 | [See `camera_web `][1] |
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('  Incorrect OS capitalization: android, ios, MacOS, web\n'
              '    Please use standard capitalizations: Android, iOS, macOS, Web\n'),
          contains('Incorrect OS support formatting'),
        ]),
      );
    });
  });

  group('code blocks', () {
    test('fails on missing info string', () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir);

      package.readmeFile.writeAsStringSync('''
Example:

```
void main() {
  // ...
}
```
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Code block at line 3 is missing a language identifier.'),
          contains('Missing language identifier for code block'),
        ]),
      );
    });

    test('allows unknown info strings', () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir);

      package.readmeFile.writeAsStringSync('''
Example:

```someunknowninfotag
A B C
```
''');

      final List<String> output = await runCapturingPrint(runner, <String>[
        'readme-check',
      ]);

      expect(
        output,
        containsAll(<Matcher>[
          contains('Running for a_package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('allows space around info strings', () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir);

      package.readmeFile.writeAsStringSync('''
Example:

```  dart
A B C
```
''');

      final List<String> output = await runCapturingPrint(runner, <String>[
        'readme-check',
      ]);

      expect(
        output,
        containsAll(<Matcher>[
          contains('Running for a_package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('passes when excerpt requirement is met', () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir);

      package.readmeFile.writeAsStringSync('''
Example:

<?code-excerpt "main.dart (SomeSection)"?>
```dart
A B C
```
''');

      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check', '--require-excerpts']);

      expect(
        output,
        containsAll(<Matcher>[
          contains('Running for a_package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('fails on missing excerpt tag when requested', () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir);

      package.readmeFile.writeAsStringSync('''
Example:

```dart
A B C
```
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['readme-check', '--require-excerpts'],
          errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Dart code block at line 3 is not managed by code-excerpt.'),
          contains('Missing code-excerpt management for code block'),
        ]),
      );
    });
  });
}
